﻿/*----------------------------------------------------------------
//  <copyright file="AliyunSmsSender.cs" company="MicroCloud@151504200868">
//      Copyright © 2020-2024 MicroCloud Corporation, All rights reserved.
//  </copyright>
//  <site>https://gitee.com/chenmm123/microclouds</site>
//  <last-editor>cmm</last-editor>
//  <last-date>2023-12-06 10:22</last-date>
//----------------------------------------------------------------*/

namespace MicroCloud.SMS.Sender
{
    #region "阿里云短信发送者"
    /// <summary>
    /// 阿里云短信发送者
    /// </summary>
    public class AliyunSmsSender : ISmsSender
    {
        #region 属性
        #region "获取 服务提供者对象"
        /// <summary>
        /// 获取 服务提供者对象
        /// </summary>
        protected IServiceProvider ServiceProvider { get; }
        #endregion
        #region "获取 事件总线"
        /// <summary>
        /// 获取 事件总线
        /// </summary>
        protected IEventBus EventBus => ServiceProvider.GetService<IEventBus>();
        #endregion
        #region "获取 日志对象"
        /// <summary>
        /// 获取 日志对象
        /// </summary>
        protected ILogger Logger { get; }
        #endregion
        #region "获取 短信配置选项"
        /// <summary>
        /// 获取 短信配置选项
        /// </summary>
        protected SmsOptions SmsOpt => ServiceProvider.GetService<IConfiguration>()?.GetSmsOptions();
        #endregion

        #region "获取 HttpClient工厂对象"
        /// <summary>
        /// 获取 HttpClient工厂对象
        /// </summary>
        protected IHttpClientFactory HttpClientFactory => ServiceProvider.GetService<IHttpClientFactory>();
        #endregion

        #region "获取 短信查询功能"
        /// <summary>
        /// 获取 短信查询功能
        /// </summary>
        protected ISmsInquirer SmsInquirer => ServiceProvider.GetService<ISmsInquirer>();
        #endregion

        #endregion

        #region "构造函数"
        #region "初始化一个阿里云短信发送者的新实例"
        /// <summary>
        /// 初始化一个阿里云短信发送者 <see cref="AliyunSmsSender"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public AliyunSmsSender(IServiceProvider provider)
        {
            ServiceProvider = provider;
            Logger = provider.GetLogger(GetType());
        }
        #endregion

        #endregion

        #region "方法"
        #region "从配置文件中获取短信模板"
        /// <summary>
        /// 从配置文件中获取短信模板
        /// </summary>
        /// <param name="smsType">短信类型</param>
        /// <returns></returns>
        public virtual async Task<string> GetTemplateCodeAsync(SmsType smsType)
        {
            var templateCode = "";
            switch (smsType)
            {
                case SmsType.VerifyCode:
                    templateCode = SmsOpt.VerifyCodeTemplate;
                    break;
                case SmsType.Notice:
                    templateCode = SmsOpt.NoticeTemplate;
                    break;
                case SmsType.Promote:
                    templateCode = SmsOpt.PromoteTemplate;
                    break;
            }
            return await Task.Factory.StartNew(() => templateCode);
        }
        #endregion
        #region "发送短信"
        /// <summary>
        /// 发送短信
        /// </summary>
        /// <param name="paramsDto">短信发送参数DTO</param>
        /// <param name="phoneNumbers">接收短信的手机号码</param>
        /// <returns></returns>
        public virtual async Task<OperationResult<SendResponse>> SendAsync(SendParamsDto paramsDto, params string[] phoneNumbers)
        {
            var templateCode = await GetTemplateCodeAsync(paramsDto.SmsType);
            var result = SendByAliyun(SmsOpt.RegionId, SmsOpt.Id, SmsOpt.Secret
                        , SmsOpt.Domain, SmsOpt.SignName
                        , templateCode
                        , paramsDto.TemplateParam
                        , paramsDto.OutId
                        , paramsDto.UpExtendCode
                        , phoneNumbers);
            //触发发送成功事件
            if (result.Succeeded)
            {
                SmsSendEventData eventData = new()
                {
                    PhoneNumbers = phoneNumbers,
                    ParamsDto = paramsDto,
                    Response = result.Data,
                };
                await EventBus?.PublishAsync(this, eventData);
            }
            return await Task.Factory.StartNew(() => result);
        }
        #endregion
        #region "批量发送短信"
        /// <summary>
        /// 批量发送短信
        /// </summary>
        /// <param name="paramsDto">短信批量发送参数DTO</param>
        /// <returns></returns>
        public virtual async Task<OperationResult<SendResponse>> SendBatchAsync(BatchSendParamsDto paramsDto)
        {
            var templateCode = await GetTemplateCodeAsync(paramsDto.SmsType);
            paramsDto.GetLineParamsJsonString(out string phoneNumberJson
                , out string signNameJson
                , out string templateParamJson
                , out string smsUpExtendCodeJson);
            var result = SendBatchByAliyun(SmsOpt.RegionId, SmsOpt.Id, SmsOpt.Secret
                        , SmsOpt.Domain, signNameJson
                        , templateCode
                        , templateParamJson
                        , smsUpExtendCodeJson
                        , phoneNumberJson);
            //触发发送成功事件
            if (result.Succeeded)
            {
                SmsBatchSendEventData eventData = new()
                {
                    ParamsDto = paramsDto,
                    Response = result.Data,
                };
                await EventBus?.PublishAsync(this, eventData);
            }
            return await Task.Factory.StartNew(() => result);
        }
        #endregion

        #endregion

        #region "受保护方法"
        #region "以阿里云作为服务商发送短信"
        /// <summary>
        /// 以阿里云作为服务商发送短信
        /// </summary>
        /// <param name="regionId">区域ID cn-hangzhou</param>
        /// <param name="accessKeyId">阿里云API的密钥Id</param>
        /// <param name="accessKeySecret">阿里云API的密钥Secret</param>
        /// <param name="domain">产品域名 dysmsapi.aliyuncs.com</param>
        /// <param name="signName">短信签名-可在短信控制台中找到</param>
        /// <param name="templateCode">短信模板-可在短信控制台中找到</param>
        /// <param name="templateParam">模板中的变量Json字符串</param>
        /// <param name="outId">提供给业务方扩展字段，最终在短信回执消息中将此值带回给调用者</param>
        /// <param name="upExtendCode">上行短信扩展码，无特殊需要此字段的用户请忽略此字段。</param>
        /// <param name="phoneNumbers">接收短信的手机号码</param>
        /// <returns></returns>
        protected virtual OperationResult<SendResponse> SendByAliyun(string regionId, string accessKeyId, string accessKeySecret
            //, string Product <param name="Product">产品名称 Dysmsapi</param>
            , string domain
            , string signName, string templateCode, string templateParam, string outId
            , string upExtendCode
            , params string[] phoneNumbers
            )
        {
            regionId = regionId.IsMissing() ? AliyunConstants.RegionId : regionId;
            //Product = Product.IsMissing() ? AliyunConstants.Product : Product;
            domain = domain.IsMissing() ? AliyunConstants.Domain : domain;

            Check.NotNullOrEmpty(accessKeyId, nameof(accessKeyId));
            Check.NotNullOrEmpty(accessKeySecret, nameof(accessKeySecret));
            Check.NotNullOrEmpty(signName, nameof(signName));
            Check.NotNullOrEmpty(templateCode, nameof(templateCode));

            if (phoneNumbers.Length < 1)
            {
                throw new Exception(I18N.T("手机号码不能为空"));
            }
            if (phoneNumbers.Length > 1000)
            {
                throw new Exception(I18N.T("手机号码数量不能超过 {1000}", 1000));
            }
            var sendPhoneNumbers = phoneNumbers.Join(",");

            #region "未测试写法-注释备查"
            //IClientProfile profile = DefaultProfile.GetProfile(RegionId, AccessKeyId, AccessKeySecret);
            ////DefaultProfile.AddEndpoint(RegionId, RegionId, Product, Domain);
            //profile.AddEndpoint(RegionId, RegionId, Product, Domain);
            //IAcsClient acsClient = new DefaultAcsClient(profile);
            //SendSmsRequest request = new SendSmsRequest();
            //SendSmsResponse response = null;
            //try
            //{

            //    //必填:待发送手机号。支持以逗号分隔的形式进行批量调用，批量上限为1000个手机号码,批量调用相对于单条调用及时性稍有延迟,验证码类型的短信推荐使用单条调用的方式
            //    request.PhoneNumbers = "15000000000";
            //    //必填:短信签名-可在短信控制台中找到
            //    request.SignName = SignName;
            //    //必填:短信模板-可在短信控制台中找到
            //    request.TemplateCode = TemplateCode;
            //    //可选:模板中的变量替换JSON串,如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为
            //    request.TemplateParam = "{\"customer\":\"123\"}";
            //    //可选:outId为提供给业务方扩展字段,最终在短信回执消息中将此值带回给调用者
            //    if (!OutId.IsMissing())
            //    {
            //        request.OutId = OutId;
            //    }
            //    //请求失败这里会抛ClientException异常
            //response = acsClient.GetAcsResponse(request);

            //}
            //catch (ServerException e)
            //{
            //    Console.WriteLine(e.ErrorCode);
            //}
            //catch (ClientException e)
            //{
            //    Console.WriteLine(e.ErrorCode);
            //}
            #endregion

            //设置短息处理程序
            IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new(profile);
            CommonRequest request = new()
            {
                Method = MethodType.POST,
                Domain = domain,
                Version = AliyunConstants.Version,
                Action = AliyunConstants.SendSmsAction
            };
            // request.Protocol = ProtocolType.HTTP;

            //*必填：接收短信的手机号码。
            //国内短信：11位手机号码，例如15951955195。
            //国际&港澳台消息：国际区号 + 号码，例如85200000000。
            //支持对多个手机号码发送短信，手机号码之间以英文逗号（,）分隔。上限为1000个手机号码。批量调用相对于单条调用及时性稍有延迟。
            //说明 验证码类型短信，建议使用单独发送的方式。
            request.AddQueryParameters("PhoneNumbers", sendPhoneNumbers);

            //*必填：短信签名名称。请在控制台签名管理页面签名名称一列查看。
            //说明 必须是已添加、并通过审核的短信签名。
            request.AddQueryParameters("SignName", signName);

            //*必填：短信模板ID。请在控制台模板管理页面模板CODE一列查看。
            //说明 必须是已添加、并通过审核的短信签名；且发送国际&港澳台消息时，请使用国际&港澳台短信模版。
            request.AddQueryParameters("TemplateCode", templateCode);

            //可选：短信模板变量对应的实际值，JSON格式。
            //如模板内容为"亲爱的${name},您的验证码为${code}"时,此处的值为 {"SeonHu":"123456"}
            //说明 如果JSON中需要带换行符，请参照标准的JSON协议处理。
            if (!templateParam.IsNullOrEmpty())
            {
                request.AddQueryParameters("TemplateParam", templateParam);
            }

            //可选：上行短信扩展码，无特殊需要此字段的用户请忽略此字段。
            if (!upExtendCode.IsNullOrEmpty())
            {
                request.AddQueryParameters("UpExtendCode", upExtendCode);
            }

            //可选：外部流水扩展字段。最终在短信回执消息中将此值带回给调用者。
            if (!outId.IsNullOrEmpty())
            {
                request.AddQueryParameters("OutId", outId);
            }

            try
            {
                //发送短信
                CommonResponse response = client.GetCommonResponse(request);
                var smsSentResponse = response.Data.FromJsonString<SendResponse>();
                if (!smsSentResponse.Succeeded)
                {
                    return OperationResult.Error(smsSentResponse);
                }
                return OperationResult.Success(smsSentResponse);
            }
            catch (ServerException e)
            {
                Logger.LogError(e, "{msg}", e.Message);
                return OperationResult.Error<SendResponse>(e.Message);
            }
            catch (ClientException e)
            {
                Logger.LogError(e, "{msg}", e.Message);
                return OperationResult.Error<SendResponse>(e.Message);
            }
        }
        #endregion
        #region "以阿里云作为服务商批量发送短信"
        /// <summary>
        /// 以阿里云作为服务商批量发送短信
        /// </summary>
        /// <param name="regionId">区域ID cn-hangzhou</param>
        /// <param name="accessKeyId">阿里云API的密钥Id</param>
        /// <param name="accessKeySecret">阿里云API的密钥Secret</param>
        /// <param name="domain">产品域名 dysmsapi.aliyuncs.com</param>
        /// <param name="signNameJson">短信签名名称的JSON数组格式，如：["阿里云","阿里巴巴"]，可在短信控制台中找到</param>
        /// <param name="templateCode">短信模板-可在短信控制台中找到</param>
        /// <param name="templateParamJson">短信模板变量对应的实际值的JSON格式，如：[{"name":"TemplateParamJson1"},{"name":"TemplateParamJson2"}]</param>
        /// <param name="upExtendCodeJson">上行短信扩展码的JSON数组格式，如：["90999","90998"]，无特殊需要此字段的用户请忽略此字段。</param>
        /// <param name="phoneNumberJson">接收短信的手机号码的JSON数组格式，如：["15900000000","13500000000"]</param>
        /// <returns></returns>
        protected virtual OperationResult<SendResponse> SendBatchByAliyun(string regionId, string accessKeyId, string accessKeySecret
            //, string Product <param name="Product">产品名称 Dysmsapi</param>
            , string domain
            , string signNameJson, string templateCode, string templateParamJson
            , string upExtendCodeJson
            , string phoneNumberJson
            )
        {
            regionId = regionId.IsMissing() ? AliyunConstants.RegionId : regionId;
            //Product = Product.IsMissing() ? AliyunConstants.Product : Product;
            domain = domain.IsMissing() ? AliyunConstants.Domain : domain;

            Check.NotNullOrEmpty(accessKeyId, nameof(accessKeyId));
            Check.NotNullOrEmpty(accessKeySecret, nameof(accessKeySecret));
            Check.NotNullOrEmpty(templateCode, nameof(templateCode));

            //设置短息处理程序
            IClientProfile profile = DefaultProfile.GetProfile(regionId, accessKeyId, accessKeySecret);
            DefaultAcsClient client = new(profile);
            CommonRequest request = new();
            request.Method = MethodType.POST;
            request.Domain = domain;
            request.Version = AliyunConstants.Version;
            request.Action = AliyunConstants.SendBatchSmsAction;
            // request.Protocol = ProtocolType.HTTP;

            //*必填：接收短信的手机号码，JSON数组格式。
            //示例：["15900000000","13500000000"]
            //国内短信：11位手机号码，例如15900000000。
            //国际&港澳台消息：国际区号 + 号码，例如85200000000。
            //说明 验证码类型短信，建议使用接口SendSms单独发送。
            request.AddQueryParameters("PhoneNumberJson", phoneNumberJson);

            //*必填：短信签名名称，JSON数组格式。
            //如：["阿里云","阿里巴巴"]
            //请在控制台签名管理页面签名名称一列查看。
            //说明 必须是已添加、并通过审核的短信签名；且短信签名的个数必须与手机号码的个数相同、内容一一对应。
            request.AddQueryParameters("SignNameJson", signNameJson);

            //*必填：短信模板ID。请在控制台模板管理页面模板CODE一列查看。
            //说明 必须是已添加、并通过审核的短信签名；且发送国际&港澳台消息时，请使用国际&港澳台短信模版。
            request.AddQueryParameters("TemplateCode", templateCode);

            //可选：短信模板变量对应的实际值，JSON格式。
            //如：[{"name":"TemplateParamJson1"},{"name":"TemplateParamJson2"}]
            //说明 如果JSON中需要带换行符，请参照标准的JSON协议处理；
            //且模板变量值的个数必须与手机号码、签名的个数相同、内容一一对应，
            //表示向指定手机号码中发对应签名的短信，且短信模板中的变量参数替换为对应的值。
            if (!templateParamJson.IsNullOrEmpty())
            {
                request.AddQueryParameters("TemplateParamJson", templateParamJson);
            }

            //可选：上行短信扩展码，JSON数组格式。无特殊需要此字段的用户请忽略此字段。
            //如：["90999","90998"]
            if (!upExtendCodeJson.IsNullOrEmpty()
                && upExtendCodeJson.FromJsonString<List<string>>().Where(o => !o.IsMissing()).Count() > 0)
            {
                request.AddQueryParameters("SmsUpExtendCodeJson", upExtendCodeJson);
            }

            CommonResponse response = null;
            try
            {
                response = client.GetCommonResponse(request);
                var smsSentResponse = response.Data.FromJsonString<SendResponse>();
                if (!smsSentResponse.Succeeded)
                {
                    return OperationResult.Error(smsSentResponse);
                }
                return OperationResult.Success(smsSentResponse);
            }
            catch (ServerException e)
            {
                Logger.LogError(e, "{msg}", e.Message);
                return OperationResult.Error<SendResponse>(e.Message);
            }
            catch (ClientException e)
            {
                Logger.LogError(e, "{msg}", e.Message);
                return OperationResult.Error<SendResponse>(e.Message);
            }
        }
        #endregion

        #endregion

    }
    #endregion

}
